package org.sadko.boozemeter;

import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Random;

import android.app.Activity;
import android.content.Intent;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.animation.AnimationUtils;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

public class TremorSensor extends Activity implements SensorListener, Runnable {
	public static final long MEASUREMENT_TIME_SEC = 15;
	
	public static final int MESSAGE_STOP_MEASUREMENT = 0;
	public static final int MESSAGE_RECIEVE_RESULTS = 1;
	public static final int MESSAGE_EXIT = 2;
	
	public static final long SCORE_NORMAL 	= 50000;
	public static final long SCORE_DRINKING = 300000;
	public static final long SCORE_DRUNK 	= 1000000;
		
	public static final float[] GRAVITY = {0, 0, -SensorManager.GRAVITY_EARTH};
	
	public static final float EPS_DISP_MIN = (float) 0.2;
	public static final float EPS_A_MAX = (float) 2.5;
	public static final float EPS_A_MIN = (float) 0.4;
	
	public static final long BASE_POINTS = 10;
	
	public static MyHandler mHndl;
	private SensorManager mSensorManager;
	
	private long[] mCounts = new long[3];
	private long[] mMults = new long[3]; 
	private float[] mVelocities = new float[3];
	private float mYaw = 0, mPitch = 0, mRoll = 0;
	private float[] mCorr = new float[3];
	private float[] mCurG = new float[3];
	private boolean mOrientUpToDate = false;
	
	private long progress = 0;
	private long mLastChangeTime;
	private long mLastImgChangeTime;
	private long mInitTime;
	private int  mLastImg;
	private ProgressBar mProgBar;
	private TextView mTv;
	private ImageSwitcher mIv;
	private Random generator = new Random();
	
	private ArrayList<Float> mAccX = new ArrayList();
	private ArrayList<Float> mAccY = new ArrayList();
	private ArrayList<Float> mAccZ = new ArrayList();
	
	
	private class MyHandler extends Handler{
		@Override
		public void handleMessage(Message msg){
			switch(msg.what){
			case MESSAGE_STOP_MEASUREMENT:{
				mSensorManager.unregisterListener(TremorSensor.this);
				Thread thread = new Thread(TremorSensor.this);
				thread.start();
				break;
			}
			case MESSAGE_RECIEVE_RESULTS:{
				Intent intent = new Intent(TremorSensor.this, ResultShower.class);
				intent.putExtra("Result", mCounts[0]+mCounts[1]+mCounts[2]);
				startActivity(intent);
				finish();
				break;	
			}
			}
		}

	}
	
	private void updateUI() {
		mTv.setText(String.valueOf(MEASUREMENT_TIME_SEC-progress++));
		mProgBar.setProgress((int)(MEASUREMENT_TIME_SEC-progress)*100/15);
		
		if(Math.abs(System.currentTimeMillis() - mLastImgChangeTime) < 3000) return;
		
		int nextImg = mLastImg;
		while(nextImg == mLastImg) nextImg = generator.nextInt(3);
		switch(nextImg){
			case 0:{
				mIv.setImageResource(R.drawable.droid_drinking);
				break;
			}
			case 1:{
				mIv.setImageResource(R.drawable.droid_drunk);
				break;
			}
			case 2:{
				mIv.setImageResource(R.drawable.droid_normal);
				break;
			}
		}
		mLastImgChangeTime = System.currentTimeMillis();
		mLastImg = nextImg;
	}
	
	@Override
	public void onCreate (Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		this.setContentView(R.layout.sensor);
		
		mHndl = new MyHandler();
		mOrientUpToDate = false;
		
		mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); //USE for actual device
	    
	    mProgBar = (ProgressBar) findViewById(R.id.prog_bar);
	    
		mProgBar.setProgress((int)100);
	    
		mTv = (TextView) findViewById(R.id.time_remain);
	    mIv = (ImageSwitcher)findViewById(R.id.im_view_prog);
	    	    
	    mTv.setText(String.valueOf(MEASUREMENT_TIME_SEC-progress++));
	    
	    ImageView v = new ImageView(this);
	    v.setImageResource(R.drawable.droid_normal);
	    mIv.addView(v);
	    v = new ImageView(this);
	    v.setImageResource(R.drawable.droid_drinking);
	    mIv.addView(v);
	   	    
	    mIv.setAnimateFirstView(true);
	    mIv.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
	    mIv.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));
	    
	  	mSensorManager.registerListener(TremorSensor.this, SensorManager.SENSOR_ORIENTATION | SensorManager.SENSOR_ACCELEROMETER,SensorManager.SENSOR_DELAY_FASTEST);
		Message msg = Message.obtain();
		msg.what = MESSAGE_STOP_MEASUREMENT;
		mHndl.sendMessageDelayed(msg, MEASUREMENT_TIME_SEC*1000);
		mLastChangeTime = System.currentTimeMillis();
		mLastImgChangeTime = System.currentTimeMillis();
		mInitTime = System.currentTimeMillis();
	}
	
	public void run(){
		handleCounts();
		Message msg = Message.obtain();
		msg.what = MESSAGE_RECIEVE_RESULTS;
		mHndl.sendMessage(msg);
	}
	
	public void onPause(){
		mSensorManager.unregisterListener(this);
		mHndl.removeMessages(MESSAGE_STOP_MEASUREMENT);
		finish();
		super.onPause();
	}
	
	@Override
	public void onDestroy(){
		mSensorManager.unregisterListener(this);
		mOrientUpToDate = false;
				
		super.onDestroy();
	}

	public void onAccuracyChanged(int sensor, int accuracy) {
		// TODO Auto-generated method stub
		
	}

	public void onSensorChanged(int sensor, float[] values) {
		switch(sensor){
		case SensorManager.SENSOR_ACCELEROMETER:{
			if(!mOrientUpToDate) return;
			if(Math.abs(mInitTime - System.currentTimeMillis()) < 300) return;
			mAccX.add(values[0] - mCurG[0]);
			mAccY.add(values[1] - mCurG[1]);
			mAccZ.add(values[2] - mCurG[2]);
			if(Math.abs(mLastChangeTime - System.currentTimeMillis())>1000){
				updateUI();
				mLastChangeTime = System.currentTimeMillis();
			}
			break;
		}
		case SensorManager.SENSOR_ORIENTATION:{
			mYaw   = values[0];
			mPitch = values[1];
			mRoll  = values[2];
			updateG(mYaw, mPitch, mRoll);
			mOrientUpToDate = true;
			break;
		}
		}
	}
	
	private void refineAccels(){
		ListIterator<Float> itrX = mAccX.listIterator();
		ListIterator<Float> itrY = mAccY.listIterator();
		ListIterator<Float> itrZ = mAccZ.listIterator();
		float[] mean = new float[3];
		int size = mAccZ.size(); //overinsuring,that there won't be simultaneous reading from Lists 
		for(int i = 0; i < size; i++){
			mean[0] += (Float)itrX.next();
			mean[1] += (Float)itrY.next();
			mean[2] += (Float)itrZ.next();
		}
		mCorr[0] = mean[0]/mAccX.size();
		mCorr[1] = mean[1]/mAccY.size();
		mCorr[2] = mean[2]/mAccZ.size();
	}
	
	private void handleCounts() {
		refineAccels();
		ListIterator<Float> itrX = mAccX.listIterator();
		ListIterator<Float> itrY = mAccY.listIterator();
		ListIterator<Float> itrZ = mAccZ.listIterator();
		float[] a = new float[3];
		int size = mAccZ.size();
		for(int i = 0; i < size; i++){
			a[0] = (Float)itrX.next()- mCorr[0];
			a[1] = (Float)itrY.next()- mCorr[1];
			a[2] = (Float)itrZ.next()- mCorr[2];
			updateCounts(a);
		}
	}
	
	private void updateCounts(float[] a) {
		for(int i = 0; i < 3; i++){
			
			int sign = (mVelocities[i] > 0)?1:-1; //we want to know, if displacement changes sign after update, 
												  //i.e. if movement stopped and we need to reset multiplier
			mVelocities[i] += a[i];
			
			if (Math.abs(mVelocities[i]) > EPS_DISP_MIN && mVelocities[i]*sign > 0 
					&& Math.abs(a[i]) > EPS_A_MIN && Math.abs(a[i]) < EPS_A_MAX) 
				mMults[i] += 5;
			else mMults[i] = 0;
			
			mCounts[i] += mMults[i] * BASE_POINTS;
	
		}
	}

	private float[] vectMatrMult(float[] in, float[][] rot){
		float[] out = new float[3];
		
		for(int i = 0; i<3; i++)
			for(int j = 0; j<3; j++){
				out[i] += rot[i][j]*in[j];
			}
		return out;
	}
	
	private void updateG(float yaw, float pitch, float roll){
		float[] out = new float[3]; 
		float[][] rotX = new float [3][3];
		float[][] rotY = new float [3][3];
		for(int i = 0; i<3; i++)
			for(int j = 0; j<3; j++){
				rotX[i][j] = 0;
				rotY[i][j] = 0;
			}
		
		float sine = (float) Math.sin(pitch*Math.PI/180.0);
		float cosine = (float) Math.cos(pitch*Math.PI/180.0);
		rotX[0][0] = 1;
		rotX[1][1] = cosine;
		rotX[1][2] = -sine;
		rotX[2][1] = sine;
		rotX[2][2] = cosine;
		out = vectMatrMult(GRAVITY, rotX);
		
		sine = (float) Math.sin(roll*Math.PI/180.0);
		cosine = (float) Math.cos(roll*Math.PI/180.0);
		rotY[1][1] = 1;
		rotY[0][0] = cosine;
		rotY[0][2] = -sine;
		rotY[2][0] = sine;
		rotY[2][2] = cosine;
		out = vectMatrMult(out, rotY);
		
		for(int i = 0; i < 3; i++)
			mCurG[i] = out[i];
		
	}
}
